iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 23
2
Modern Web

成為 Modern PHPer系列 第 23

Day 23:Class 的 Magic Method

  • 分享至 

  • xImage
  •  

前言

Magic Method 是擴展 Class 能力的一項特性,它可能是為了完成某些目的而被設計出來的。

目前 PHP 支援的 Magic Method 有以下幾種:

  • __construct() / __destruct()
  • __call() / __callStatic()
  • __get() / __set() / __unset() / __isset()
  • __sleep() / __wakeup()
  • __toString()
  • __invoke()
  • __set_state()
  • __clone()
  • __debugInfo()

註:所有的 Magic Method 都以 __ 為前綴,除了內建的外,建議不要用 __ 為前綴命名 methods
註:Magic Method 必須為 public function,但如果硬是使用 protectedprivate 也不會報錯

Magic Method 的用法

__construct()__destruct()

與大部份的物件導向程式語言類似,為了能在建立/銷毀物件時指定其行為,PHP 提供 __construct()__destruct()

<?php

class File
{
    protected $file;

    public function __construct(string $filename)
    {
        $this->file = fopen(string $filename, 'wb');
    }
    
    public function writeString(string $input)
    {
        fwrite($this->file, $input);
    }
    
    public function __destruct()
    {
        fclose($this->file);
    }
}

$file = new File('test.txt');
$file->writeString('Hello World');

自上述範例中,在 __construct() 中開啟指定資源,並在 __destruct() 中關閉該資源,這算是標準用法。

__construct() 會在 new 的時候被調用,__destruct() 則在該物件被銷毀時調用。

物件的生命周期:已經被 new 出來的物件,在函式結束時、unset()時、沒有變數參照時、程式執行結束時、調用 exit() 時都會被銷毀。

__call()__callStatic()

為了能夠動態建立 Class Method 而衍生這兩個 Magic Method。

<?php

class A
{
    protected $number1 = 1;
    protected $number2 = 2;
    protected $number3 = 3;
    
    protected stattic $str = 'hello';
    
    public function __call(string $name, array $args)
    {
        switch($name) {
            case 'number1': return $this->number1;
            case 'number2': return $this->number2;
            case 'number3': return $this->number3;
            default: return null;
        }
    }
    
    public function __callStatic(string $name, array $args)
    {
        return self::$str;
    }
}

$a = new A();
$a->number1(); // 1
$a->number2(); // 2
$a->number3(); // 3
$a->number4(); // null
A::foo(); // hello
A::bar(); // hello

註:在 Laravel 中,有許多神奇的功能都是倚靠這個 Magic Method 實現的。

__get(), __set(), __isset()__unset()

  • 取不存在或不可見的值時,__get() 會被調用
  • 設不存在或不可見的值時,__set() 會被調用
  • 對不存在或不可見的值調用 isset()empty() 時,__isset() 會被調用
  • 對不存在或不可見的值調用 unset() 時,__unset() 會被調用

「不存在或不可見的值」,表示為在目前的作用域無法存取到的 attributes,可能是沒有被宣告到,或是在外部存取 protectedprivate 的值。

當時這些 Magic Method 的設計背景應該是為了簡化 Java 的 Getter 及 Setter 模式,但因為大部份的 IDE 都無法正常解析,在正常撰寫程式時通常不會使用。

註:不過 Laravel 裡大量用到這些功能,事實上在適度的封裝後它是好用的,惟效能上較低落(經過簡單實測,大概差 9 倍效率)

__sleep()__wakeup()

在談這兩個 Magic Method 之前,需要先介紹兩個函式:serialize()unserialize()

serialize() 可以將資料轉換為字串,除了以下兩種資料無法轉換外,其餘都可以轉為可儲存的字串:

  • resource
  • 大部份的內建 Class

__sleep() 會在 serialize() 被呼叫時調用,用於清理一些無法儲存的資料(例如 resource),或是斷開資料庫連線等等。

__wakeup() 會在 unserialize() 被呼叫時調用,用於重新建立部份資料,或是重新連接資料庫。

值得注意的是,如果 Class 實現了 Serializable 的話,__sleep()__wakeup() 將不再有效,會被 public function serialize()public function unserialize() 取代。

__toString()

這個 Magic Method 會在 Class 被轉型為 string 時呼叫,例如被 echoprint 時應該回應什麼。

class Stringable
{
    protected $str = 'Hello';
    
    public function __toString()
    {
        return $this->str;
    }
}

$s = new Stringable();
echo $s; // Hello

__invoke()

這個 Magic Method 會在 Class 被當成函式時應該如何執行。

class Invokable
{
    public function __invoke(string $name = 'World')
    {
        echo "Hello $name";
    }
}

(new Invokable)(); // Hello World
(new Invokable)('Jack'); // Hello Jack

__clone

這個 Magic Method 會在對 Class 使用 clone 時被調用。

例如有時複製一個資源,但希望 ID 能夠有所變化:

<?php

class User
{
    public static $id = 1;
    
    public function __clone()
    {
        return ++self::$id;
    }
}

$user1 = new User;
$user2 = clone $user1;

$user1::$id; // 1
$user2::$id; // 2

__debugInfo()

這個 Magic Method 可以修改 var_dump()print_r() 的顯示結果。

<?php

class A
{
    private $a = 10;
}

class B
{
    private $a = 10;
    
    public function __debugInfo()
    {
        return ['a' => 123];
    }
}

var_dump(new A);
// object(A)#2355 (1) {
//     ["a":"A":private] =>
//     int(10)
// }
var_dump(new B);
// object(B)#2380 (1) {
//     ["a"]=>
//     int(123)
// }

後記

有一個 Magic Method 被我自動忽略了。

__set_state(),因為它的行為是真的詭異。

官方文件表示它是與 var_export() 做搭配,var_export()var_dump() 的差異在於 var_export() 會回傳符合 PHP 語法的字串,可以搭配 eval() 運用。

不過我真的想不到有哪裡會用這種奇怪的寫法,所以 __set_state() 就自動被我忽略了。


上一篇
Day 22:Closure 匿名函式
下一篇
Day 24:使用 Travis CI
系列文
成為 Modern PHPer30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言